package ws

import (
	"encoding/json"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"log"
	"net/http"
	"time"
	common_constants "trpg-common/constants"
	"trpg-common/logger"
	"trpg-common/utils/json_util"
	"trpg-common/utils/oss_util"
	"trpg-common/utils/time_util"
	"trpg-message/commands"
	"trpg-message/entity"
	"trpg-message/kafka"
)

const (
	// Time allowed to write a message to the peer.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the peer.
	pongWait = 60 * time.Second

	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	// Maximum message size allowed from peer.
	maxMessageSize = 10240
)

var (
	newline = []byte{'\n'}
	space   = []byte{' '}
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	// 解决跨域问题
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

// Client is a middleman between the websocket connection and the hub.
type Client struct {
	hub *Hub

	// The websocket connection.
	conn *websocket.Conn

	// Buffered channel of outbound messages.
	send chan entity.Message

	// roomId
	roomId int64

	uid string
}

// readPump pumps messages from the websocket connection to the hub.
//
// The application runs readPump in a per-connection goroutine. The application
// ensures that there is at most one reader on a connection by executing all
// reads from this goroutine.
func (c *Client) readPump() {
	defer func() {
		c.hub.unregister <- c
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		msg := entity.Message{}
		err := c.conn.ReadJSON(&msg)
		if err != nil {
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				logger.Logger.Errorf("error in sending msg. uid: %s; roomId: %d, err: %v", c.uid, c.roomId, err)
			}
			break
		}
		logger.Logger.Debugf("user %s in room %d send message: %+v", c.uid, c.roomId, msg)
		msg.GmtCreated = time_util.Now()
		msg.GmtModified = time_util.Now()
		msg.Modifier = msg.Creator
		if msg.Files != nil && len(msg.Files) != 0 {
			files, _ := json.Marshal(msg.Files)
			msg.FilesStr = string(files)
		}
		marshal, err := json.Marshal(&msg)
		if err != nil {
			logger.Logger.Errorf("%+v", errors.Wrap(err, "error in converting message to json"))
		} else {
			kafka.WriteByConn(marshal)
		}
		c.hub.broadcast <- msg
		dispatch, result := commands.Dispatch(msg)
		if dispatch {
			resultMsg := entity.Message{}
			resultMsg.RoomId = msg.RoomId
			resultMsg.Content = result
			resultMsg.Creator = "bot"
			resultMsg.CreatorName = "骰娘"
			resultMsg.Avatar = oss_util.GetUrlByKey("/avatar/bot.jpg")
			resultMsg.GmtCreated = time_util.Now()
			resultMsg.GmtModified = time_util.Now()
			resultMsg.Modifier = msg.Creator
			resultMsg.IsDeleted = common_constants.DeleteNo
			resultMarshal, err := json.Marshal(&resultMsg)
			if err != nil {
				logger.Logger.Errorf("%+v", errors.Wrap(err, "error in converting message to json"))
			} else {
				kafka.WriteByConn(resultMarshal)
			}
			c.hub.broadcast <- resultMsg
		}
	}
}

// writePump pumps messages from the hub to the websocket connection.
//
// A goroutine running writePump is started for each connection. The
// application ensures that there is at most one writer to a connection by
// executing all writes from this goroutine.
func (c *Client) writePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()
	for {
		select {
		case message, ok := <-c.send:
			logger.Logger.Infof("user %s in room %d get message: %+v", c.uid, c.roomId, message)
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				// The hub closed the channel.
				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			w.Write(json_util.Marshal(&message))

			// Add queued chat messages to the current websocket message.
			n := len(c.send)
			for i := 0; i < n; i++ {
				w.Write(newline)
				m := <-c.send
				w.Write(json_util.Marshal(&m))
			}
			if err := w.Close(); err != nil {
				return
			}
		case <-ticker.C:
			c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// serveWs handles websocket requests from the peer.
func serveWs(roomId int64, uid string, hub *Hub, w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	client := &Client{hub: hub, conn: conn, send: make(chan entity.Message, 256), roomId: roomId, uid: uid}
	client.hub.register <- client

	// Allow collection of memory referenced by the caller by doing all work in
	// new goroutines.
	go client.writePump()
	go client.readPump()
}
